﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;

namespace Foundry.Graphics
{

    public interface IBufferInfo
    {
        int Count { get; }
        int ElementSize { get; }
        int SizeInBytes { get; }
    }


	/// <summary>
	/// Represents a GPU buffer. Used by vertex and index buffers.
	/// </summary>
    public abstract class Buffer<T> : IBufferInfo, IHandle, IDisposable
        where T : struct
    {
        public int Handle { get; private set; }
        public int Count { get; private set; }
        public bool Disposed { get; private set; }
        public int ElementSize { get; private set; }
        public int SizeInBytes { get { return ElementSize * Count; } }
        public event EventHandler Disposing;

        protected BufferTarget target;

        T[] data;

        internal Buffer(BufferTarget target)
        {
            OpenTK.Graphics.GraphicsContext.Assert();
            this.target = target;
            ElementSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
            int new_handle = 0;
            GL.GenBuffers(1, out new_handle);
            Handle = new_handle;
			
			Extensions.AssertGL();
        }

        public virtual void MakeCurrent()
        {
            CheckValidity();
            GL.BindBuffer(target, Handle);
			Extensions.AssertGL();
        }
        public virtual void MakeNullCurrent()
        {
            GL.BindBuffer(target, 0);
        }

        public void ClearClientData()
        {
            data = null;
        }
        public void BufferData(BufferUsageHint usage)
        {
            CheckValidity();
            MakeCurrent();
            GL.BufferData<T>(target, new IntPtr(ElementSize * Count), data, usage);
			Extensions.AssertGL();
            //MakeNullCurrent();
        }
        protected void CheckValidity()
        {
            if (Disposed)
                throw new ObjectDisposedException(GetType().Name);
        }
		/*
        public void BufferData(int start, int count, BufferUsageHint usage)
        {
            MakeCurrent();
            CheckValidity();

            var handle = System.Runtime.InteropServices.GCHandle.Alloc(data, System.Runtime.InteropServices.GCHandleType.Pinned);
            IntPtr offset = IntPtr.Zero;
            if (IntPtr.Size == 4)
            {
                var new_addr = handle.AddrOfPinnedObject().ToInt32();
                new_addr += start * ElementSize;
                offset = new IntPtr(new_addr);

            }
            else if (IntPtr.Size == 8)
            {
                var new_addr = handle.AddrOfPinnedObject().ToInt64();
                new_addr += start * ElementSize;
                offset = new IntPtr(new_addr);
            }
            else
                throw new NotSupportedException("Unsupported pointer size");
            
            GL.BufferData(target, new IntPtr(count * ElementSize), offset, usage);

            handle.Free();
            MakeNullCurrent();
        }*/

        public void Allocate(int count)
        {
            data = new T[count];
            Count = count;
        }
        public void Allocate(IEnumerable<T> generator)
        {
            if (Disposed)
                throw new ObjectDisposedException(GetType().FullName);
            data = generator.ToArray();
            Count = data.Length;
        }

        public T this[int index]
        {
            get { return data[index]; }
            set { data[index] = value; }
        }

        public virtual void Dispose()
        {
			if(OpenTK.Graphics.GraphicsContext.CurrentContext != null && !Disposed)
			{
	            GC.SuppressFinalize(this);
	            if (Disposing != null)
	                Disposing(this, EventArgs.Empty);
	            int handle = Handle;
	            GL.DeleteBuffers(1, ref handle);
	            Handle = 0;
	            Disposed = true;
			}
        }
		~Buffer()
		{
			var handle =Handle;
			Foundry.Engine.App.CurrentApplication.DispatchResourceCallStack(() => GL.DeleteBuffers(1, new int[]{handle}));
		}
    }
}
